ACME Certificate Management

The Automatic Certificate Management Environment (ACME) modules obtain, install, renew, and revoke Transport Layer Security (TLS) certificates. They implement the client side of RFC 8555 and work with Let's Encrypt or another compatible ACME service. The modules are shared by the Barracuda App Server (BAS), Mako Server, and Xedge.

A certificate lets an HTTPS client verify the identity of your server. ACME automates obtaining and renewing that certificate. Before choosing a configuration, decide how the certificate authority (CA) will verify that your server is authorized to use its name:

Validation methodUse it whenConnection required
HTTP-01The ACME service can connect directly to the server by its public domain name.Inbound HTTP on public TCP port 80, plus outbound HTTPS to the ACME service.
Automatic DNS-01The server is on a private network and a SharkTrust portal manages its DNS record.Outbound HTTPS to both the portal and the ACME service.
SharkCA private enrollmentYou manage the trust root for private .local names or local IPv4 addresses.The SharkCA portal over outbound HTTPS.
Manual DNS-01An operator can create the required DNS TXT record.The public DNS record. The server still needs outbound HTTPS access to the ACME service.

Automatic DNS-01 is useful for memory-constrained embedded systems because it does not require a public inbound connection. It works with Xedge or with an application built directly from the Barracuda App Server libraries.

HTTP-01 proves control by serving a temporary file on your server. DNS-01 proves control by publishing a temporary text (TXT) record in the Domain Name System (DNS). SharkCA instead uses authenticated device enrollment and a private trust root.

Choose your entry point. On Mako Server, start with the mako.conf settings. On Xedge, use the certificate-settings interface. Those hosts create the runtime for you. To manage certificates from Lua, start with the examples below and acme/runtime. The DNS adapter, certificate manager, and protocol engine are covered later for custom integrations.

Start with staging for public certificates. The examples select the Let's Encrypt staging service with production=false. Staging certificates are not trusted by browsers, but staging lets you test without consuming production rate limits. Change the setting to true only after the complete flow works. SharkCA uses its own private CA and has no staging selector.

Start with a Working Configuration

The four examples use the same runtime on Mako Server and Xedge. The runtime selects the host's writable I/O automatically and installs certificates in the standard ba.slcon and ba.slcon6 HTTPS listeners. An application that embeds the BAS library directly must supply an io option and the install callback described under Runtime.create(). You can also supply these options on Mako or Xedge when using different storage or HTTPS listeners.

Before running an example, make sure networking, the system clock, a trusted HTTPS client, and private writable storage are ready. Replace the sample domain and email with your own values. See runtime and build requirements for custom BAS hosts.

The notify(code, msg) function receives a numeric lifecycle code and, for two events, an optional message string. The startup callback reports whether certificate management started successfully. Do not log private keys, SharkTrust credentials, zone secrets, proof values, or authorization headers.

HTTP-01 Example

Use the same configuration table on Mako, Xedge, or a custom BAS host. Omitting challenge selects HTTP-01. The certificate authority must reach the device on public TCP port 80.

local runtime, err = require"acme/runtime".create {
   config = {
      acceptTerms = true, -- After accepting the provider's terms.
      email = "operator@example.com",
      domains = {"device.example.com"},
      production = false
   },
   -- These messages contain lifecycle codes and optional non-secret details.
   notify = function(code, msg) trace("ACME event ", code, msg or "") end
}
assert(runtime, err and (err.message or err.code))
runtime:start(function(result, problem)
   if not result then
      trace("ACME startup failed: ", problem.message or problem.code)
   end
end)

Automatic DNS-01 with SharkTrust

A SharkTrust portal publishes and removes the TXT record. When credentials are omitted, the shared DNS module uses the host's compiled etokengen or tokengen identity.

local runtime, err = require"acme/runtime".create {
   config = {
      acceptTerms = true, -- After accepting the provider's terms.
      email = "operator@example.com",
      domains = {"controller"},
      production = false,
      namePolicy = "exact",
      challenge = {type="dns-01", dns="local"}
   },
   notify = function(code, msg) trace("ACME event ", code, msg or "") end
}
assert(runtime, err and (err.message or err.code))
runtime:start(function(result, problem)
   -- The runtime retries startup failures while it remains open.
   if not result then
      trace("ACME startup failed: ", problem.message or problem.code)
   end
end)

For an explicit identity, set challenge.portalUrl, challenge.zoneKey, and challenge.proof(message) together. The client does not accept a secret option. A host callback calculates the proof from provisioned credentials. Keep credentials outside the application ZIP and source repository. The configuration fields are described below.

The runtime uses the first domain as the requested device name, then manages the name assigned by the portal. It stores registration with the ACME state unless the host supplies a custom store. Local IPv4 discovery is automatic; forward later address changes with runtime:setIpAddress(newIpAddress, callback).

If the configured portal or zone identity differs from the saved registration, startup automatically enrolls with the configured identity and replaces the local registration. It does not remove the device from the previous portal. Switching back therefore requires a new registration; namePolicy="exact" can report name_unavailable if the old device still owns that name.

Manual DNS-01 Example

Manual mode pauses issuance until an operator publishes the TXT record. It uses the same constructor; no separate adapter setup is required.

local runtime, err
runtime, err = require"acme/runtime".create {
   config = {
      acceptTerms = true, -- After accepting the provider's terms.
      email = "operator@example.com",
      domains = {"device.example.com"},
      production = false,
      challenge = {type="dns-01", mode="manual"}
   },
   notify = function(code)
      if code == 32 then
         local record = runtime.challenge:status()
         print("Create TXT record:", record.recordName, record.recordData)
      end
   end
}
assert(runtime, err and (err.message or err.code))
runtime:start(function(result, problem)
   -- Manual issuance stays pending until the operator confirms the TXT record.
   if not result then
      trace("ACME startup failed: ", problem.message or problem.code)
   end
end)

After a public DNS lookup returns the exact TXT value, continue the pending operation:

local challenge = runtime.challenge
if challenge:status().phase == "publish" then
   challenge:continue(function(ok, problem)
      -- This confirms publication; the startup callback reports final completion.
      if not ok then trace("DNS confirmation failed: ", problem.message or problem.code) end
   end)
end

The operator removes the TXT record after validation. Use runtime.challenge:cancel() to cancel the pending publication step.

Private Certificates with SharkCA

Use SharkTrust Private CA (SharkCA) for certificates covering private device names such as controller.local, local IPv4 addresses, or both. Devices enroll with your portal and obtain certificates over outbound HTTPS. They do not need a public DNS record or an inbound HTTP challenge.

Before running this example:

local runtime, err = require"acme/runtime".create {
   config = {
      domains = {"controller"}, -- Request a device name in the portal's zone.
      sharkca = {} -- Use the compiled identity for this SharkCA zone.
   },
   notify = function(code, msg) trace("ACME event ", code, msg or "") end
}
assert(runtime, err and (err.message or err.code))
runtime:start(function(result, problem)
   -- Startup enrolls the device and installs its certificate.
   if not result then
      trace("SharkCA startup failed: ", problem.message or problem.code)
   end
end)

Use domains={} for an IP-only certificate. The zone must permit IP issuance and the device's local address. SharkCA needs no email address, terms acceptance, or production/staging selector. Its certificates use Elliptic Curve Cryptography (ECC).

To enter credentials without rebuilding the host, use Mako's acme.sharkca settings or the Xedge certificate-settings interface. For an explicit identity in Lua, see SharkCA configuration. Choose one owner for the host's HTTPS certificates; do not also start this runtime when Mako or Xedge already manages them.

SharkCA issues certificates but does not provide DNS services. Named devices normally use multicast DNS (mDNS) on the local network. Mako's configuration and Xedge's settings provide name advertisement; a runtime created directly from Lua needs the host's name-resolution integration. Portal administration and trust distribution are covered in the SharkCA user manual.

Application Lifecycle

Call runtime:close(callback) during application unload. It cancels owned work, removes an active challenge when possible, closes network clients, and prevents new operations. On a device that starts before its clock is valid, wait for the clock synchronization event before calling start(). Certificate validation and HTTPS server trust checks require a correct clock.

Mako users can configure the same runtime in mako.conf. Xedge users normally configure it in the Xedge certificate user interface.

In mako.conf, acme.challenge.secret is an optional 64-character hexadecimal string that can replace proof when supplied with portalUrl and zoneKey. Mako creates the proof callback before calling the shared runtime; an explicit proof callback takes precedence. Direct callers of Runtime.create() and Dns.createClient() must still supply a proof callback for an explicit identity.

Module Guide

Public moduleUse it for
acme/runtimeThe normal entry point for startup, certificate management, and shutdown. createManager() exposes certificate management separately for custom hosts.
acme/dnsAutomatic and manual DNS-01. createClient() exposes direct SharkTrust enrollment, DNS commands, address updates, and reverse connections.
acme/engineACME protocol operations, renewal information, software and Trusted Platform Module (TPM) keys, and HTTP-01 challenges.
acme/httpShared HTTPS trust settings, including a private CA store for a SharkCA portal.

The certificate manager is created through acme/runtime; the SharkTrust client is created through acme/dns. You do not need separate manager or SharkTrust modules. The package also contains acme/_util and acme/_server, which are private host implementation details.

Runtime and Build Requirements

The modules require the auxiliary Lua bindings, an HTTPS client with a trusted CA store, a valid system clock, and private writable storage. The I/O interface is the BAS object used to read and write that storage. A BAS build that creates software keys and certificate signing requests (CSRs) must enable these SharkSSL options:

#define SHARKSSL_ENABLE_ASN1_KEY_CREATION 1
#define SHARKSSL_ENABLE_RSAKEY_CREATE     1
#define SHARKSSL_ENABLE_ECCKEY_CREATE     1
#define SHARKSSL_ENABLE_CSR_CREATION      1

Add missing definitions to the platform's inc/arch/[platform]/TargConfig.h or compiler options. RSA certificate keys also require RSA support in the SharkSSL build. Mako Server and Xedge packages that include the ACME plugin provide the required integration.

A Trusted Platform Module (TPM) can hold private ECC keys and perform signing without exposing those keys to Lua. The stack uses the host's TPM interface when available; otherwise it creates software keys. See TPM interface for a custom host.

Common API Conventions

Callbacks and Errors

Asynchronous methods use callback(result, err). On success, result contains a value and err is nil. On failure, result is nil and err is a structured error table. A storage load() callback may return nil, nil to mean that no saved state exists.

{
   code = "bad_nonce",
   message = "The ACME server rejected the replay nonce",
   temporary = true,
   status = 400,
   type = "urn:ietf:params:acme:error:badNonce",
   detail = "...",
   retryAfter = 15,
   operation = "newOrder",
   url = "https://acme.example/directory/new-order"
}

A method may return nil, err before it starts, usually because an argument is invalid or the object is closed. After an asynchronous operation starts, its callback is invoked once. Transport errors can include cause and phase, where the phase is request, write, or read.

Lua callers must supply the documented argument types, supported option values, and callbacks. Internal calls do not repeat every argument or interface check. Incorrect Lua API use is a programming error; do not depend on an invalid_* result for every malformed argument. Network responses, persisted credentials, service identity, terms acceptance, and operation lifecycle still receive the required checks.

Lifecycle Notifications

The optional notify(code, msg) callback reports lifecycle events. code is an integer. msg is a string for codes 3 and 13, and nil for the other codes. Use these events to update a user interface or write an application log. The callback returns no value, must return promptly, and must not yield. Callback errors are caught and traced.

CodeMeaning and optional message
1Certificate management starting.
2Certificate management ready.
3Startup attempt failed. msg is the error code string, including on automatic retries. A supplied start completion callback also receives the structured error for its attempt. Mako and Xedge log the code as ACME event 3: <error code>.
4Startup retry scheduled.
10Device enrollment starting.
11Device enrolled.
12Saved registration being restored.
13Registration confirmed after enrollment or startup restoration. msg is the full assigned domain name, or an approved identifier for an IP-only SharkCA registration.
20Reverse connection enabled. This does not mean it is connected.
21Reverse connection disabled.
30Automatic DNS TXT record published; propagation wait starting.
31Automatic DNS TXT record successfully removed.
32Manual DNS TXT publication required. Read the manual adapter status for the record name and value.
40Certificate issued and saved; installation follows.
41Certificate renewed and saved; installation follows.
42Certificate revocation accepted and local record removed.
50ACME service profile committed.

Ignore unknown future codes. Only codes 3 and 13 carry the message described above. Certificate events do not identify a domain; refresh runtime:status() to find the current state. Use completion callbacks for operation results and detailed errors. For code 32, read challenge:status() for the TXT record, then call continue() or cancel() through the host interface.

ACME Service Options

local service = {
   production = false,
   productionUrl = "https://acme.example/production/directory",
   stagingUrl = "https://acme.example/staging/directory",
   http = {
      proxy = "proxy.example.com",
      proxyport = 8080,
      shark = trustedSharkSslClient
   }
}

The service table selects the ACME directory:

The selected directory URL identifies an ACME account and certificate profile. Staging and production state are kept separate. Changing the active directory through the manager or runtime uses a transactional service switch, described under manager:switchService().

Module acme/runtime

acme/runtime is the recommended high-level API. It creates an engine and manager, selects the HTTP or DNS challenge adapter, restores saved state, starts renewal, and closes the owned objects in the correct order.

Runtime.create(options)

Creates the high-level certificate runtime. Use this constructor for normal application integrations. Mako and Xedge configuration users do not call it because those hosts create the runtime automatically.

local Runtime = require"acme/runtime"
local runtime, err = Runtime.create(options)

Parameters

Return values

Throws

The listed validation failures return errors. Incorrect field types or missing required nested fields can raise Lua errors. Errors from supplied components and native setup can propagate. Most configuration and key validation occurs later during startup.

Common Configuration

Pass this table as options.config on every host, or as acme in mako.conf. Direct Lua callers supply the documented types. Host UI input validation stays at the UI boundary.

The optional challenge table selects type="dns-01" (string). Its optional mode is automatic (default) or manual (string). Automatic mode accepts portalUrl (HTTPS string), zoneKey (64-character hex string), and proof(message) (required function returning 32 binary bytes for a string message). See the proof callback contract. Omit the identity fields to use the compiled identity. Optional dns is local (default), wan, or both (string); reverse is a boolean defaulting to false; propagationDelay is seconds (number, default 30, zero allowed). Manual mode needs only type and mode.

SharkCA Configuration

Set config.sharkca to a table to select the private enrollment profile. An empty table uses the host's compiled zone identity. For an explicit identity, supply the following fields together:

config.domains is a required array containing zero or one device-name string. Use {"controller"} for a name or {} for IP-only issuance. The portal's zone policy selects the approved certificate names and addresses. The profile supports ECC keys only and does not accept config.challenge. Email, terms acceptance, and public production/staging fields are unused. There is no fallback to a public ACME service.

The secret and caFile fields belong to Mako's configuration loader, which derives the proof callback and loads a PEM trust file. Direct Runtime.create() callers supply the proof callback themselves and configure portal HTTPS trust when the existing trust store does not trust the portal's issuer.

Certificate Installation Callback

The manager calls this function whenever its active certificate set changes. Replace a non-empty set atomically if possible. An empty records array means the manager has no certificates to supply; the host determines how to handle its existing HTTPS listeners. Mako and Xedge applications normally omit this callback because their standard HTTPS listeners use the built-in installer.

The built-in installer intentionally leaves the currently installed certificates in place when records is empty. It does not switch to the self-signed default. Thus removing or revoking the last managed certificate does not immediately stop the listeners from presenting it. The installed set remains until another non-empty set replaces it or the host restarts. A custom installer must define its own empty-set behavior.

local function installCertificates(records, callback)
   -- Convert each record for the platform and replace the active TLS set.
   -- Call callback only after every record is active, or after the update fails.
   callback(true, nil)
end

Parameters

Return values

The installer's direct return values are ignored. Completion must use callback, synchronously or asynchronously. The manager remains busy until completion; it does not interpret a direct nil, err return as failure.

Throws

Installer exceptions and callback-reported failures become certificate_install_failed error tables. For a table error, the manager keeps its message; other error fields are not forwarded. Duplicate completion calls are ignored. An exception after completion does not replace the completed result.

SharkTrust Registration

The client discovers the local IPv4 address used for the portal connection and sends it during enrollment. The application supplies only the naming, DNS publication, and descriptive fields below.

The registration table supplies SharkTrust enrollment data:

{
   name = "controller",
   namePolicy = "exact",
   dns = "local",
   info = "BAS controller"
}

Runtime Methods

runtime:start(callback)

Starts certificate management. Call this once after storage, networking, DNS, and the system clock are ready. It restores saved certificates and SharkTrust registration, enrolls when needed, obtains missing certificates, and starts renewal.

For SharkCA, startup saves the account key before enrollment, checks the portal's sharkca-v1 capability and account association, and restores saved registration before requesting a certificate. The runtime manages one stable $device certificate record.

Parameters

The method is idempotent. Every reported startup failure is returned to the callback and retried in the background while the runtime remains open. Temporary, retryable errors use a delay starting at 30 seconds and doubling to five minutes. Other errors, including name_unavailable, retry once per hour. There is no attempt limit. Closing the runtime cancels retry. Incorrect configuration, unavailable storage or an occupied name may still need operator correction.

Return values

Throws

Reported storage, enrollment, issuance and installation failures use the callback. Completion callback errors are caught and traced. Incorrect arguments or supplied components may raise Lua errors.

runtime:isRegistered(callback)

Checks the current device registration with SharkTrust and refreshes the portal with the automatically discovered local IPv4 address. Use it for diagnostics or a registration-status user interface. Normal startup performs this work automatically.

Parameters

Return values

For SharkCA, this method resumes the saved registration and applies the portal's approved certificate identifiers. When they change, success is reported only after the replacement certificate is installed. Failure leaves the previous certificate installed. Address-change behavior and overlapping operations are described under setIpAddress().

Throws

Reported storage and portal failures use the callback. Callback errors are caught and traced. Errors raised by supplied components can propagate.

runtime:isAvailable(name, callback)

Checks whether a requested SharkTrust device name appears available. Use it only as an advisory user-interface check. Enrollment performs the authoritative check and can still report a conflict.

Parameters

Return values

Throws

Reported portal failures use the callback. Callback errors are caught and traced. Incorrect arguments or supplied components may raise Lua errors.

runtime:setIpAddress(ipAddress, callback)

Updates the local IPv4 address stored by the portal. SharkTrust refreshes the DNS A record; SharkCA replaces the certificate when the approved identifiers change. Use it after startup when DHCP or another network event reports that the device address changed. Startup discovery and the first update are automatic.

Parameters

Return values

For SharkCA, call this method after startup. The runtime saves accepted registration changes, keeps the $device record's private key, and installs a replacement certificate when the approved identifiers change. Its callback receives the portal registration result only after installation succeeds. If replacement fails, the previous certificate stays installed.

An overlapping portal update can report operation_in_progress; retry it after the current callback. If the identifiers change during issuance, the superseded request can report identifiers_changed while the runtime proceeds with the latest identifiers. Calling before SharkCA startup reports runtime_not_started.

Throws

Reported storage and portal failures use the callback. Callback errors are caught and traced. Incorrect arguments or supplied components may raise Lua errors.

runtime:reverseConnection(enable)

Changes the SharkTrust reverse-connection state immediately. Use it when an application lets an operator enable or disable portal-assisted remote access after startup.

Parameters

Return values

Throws

Reported configuration and proof failures return nil, err. Native reverse-connection option errors and exceptions from supplied components can propagate.

runtime:switchService(service, callback)

Moves certificate management to another ACME directory. Use it to change between staging and production or to select another compatible provider. Existing certificates stay installed until the new profile is ready. SharkTrust registration does not change.

Parameters

Return values

SharkCA does not switch directories through this method; its callback reports sharkca_reconfigure_required. Close the runtime and create it again with the new portal configuration.

Throws

Uses the exception and error-reporting behavior documented for manager:switchService().

runtime:renew(domain, callback)

Immediately requests a replacement certificate even when the current certificate is not near expiration. Use it for an operator-requested renewal or after a certificate-related policy change. Scheduled renewal needs no application call.

Parameters

Return values

Throws

Uses the exception and error-reporting behavior documented for manager:renew().

runtime:status()

Returns an operational snapshot for status pages, diagnostics, and health checks. The built-in components do not include private keys or device credentials.

Parameters

None. Additional arguments are ignored.

Return values

{
   loaded = true,
   started = true,
   closed = false,
   starting = false,
   retryPending = false,
   operation = nil,
   directoryUrl = "https://acme.example/directory",
   domains = {},
   jobs = {},
   lastError = nil,
   retryAt = nil,
   registration = challengeStatusOrNil,
   reverse = {enabled=true, connected=true, status=202, connections=3}
}

Throws

Exceptions raised by an underlying status method, including a custom challenge adapter's status() method, propagate. Lua allocation errors can also propagate.

runtime:close(callback)

Stops certificate management and releases owned resources. Call it during application unload. It closes the manager and challenge adapter and cancels owned work.

Parameters

Repeated close calls succeed immediately, without waiting for an earlier close to finish or retrying cleanup. Their callbacks receive true, nil.

Return values

If a custom certificate installer or automatic DNS registration operation is still pending, returns nil, err (string) with err equal to "busy". Registration operations include loading or saving state, enrollment and identity switching. Shutdown has not begun: both components remain open, timers are unchanged, and the supplied close callback is not called. Retry close after the operation finishes. The built-in installer completes synchronously.

Throws

Reported cleanup failures go to the callback as nil, err. Errors raised by the completion callback are caught and traced. A custom challenge adapter must follow its callback contract; an exception from its close() method is not converted to a cleanup error result.

runtime.challenge contains the selected challenge adapter. Most applications do not use it directly. A manual DNS user interface uses it to call status(), continue(), or cancel().

Module acme/dns

acme/dns creates DNS-01 challenge adapters. Use createSharkTrust() for automatic DNS updates or createManual() for an operator-assisted flow.

Dns.createSharkTrust(options)

Creates a DNS-01 challenge adapter that uses SharkTrust to publish TXT records and persists the device registration. Runtime.create() calls this constructor automatically when config.challenge selects automatic DNS-01. Call it directly only when assembling an engine and manager without the runtime.

local Dns = require"acme/dns"
local challenge, err = Dns.createSharkTrust {
   client = sharkTrustClient,
   store = {
      load = function(callback) callback(savedState, nil) end,
      save = function(state, callback) callback(true, nil) end
   },
   propagationDelay = 30,
   notify = function(code) end
}

Parameters

Return values

Throws

Incorrect options or a missing/incompatible client can throw. Store callbacks are used later; exceptions from store.load and store.save are converted to storage_read_failed and storage_write_failed errors.

SharkTrust State Storage

When used with acme/runtime, this store also retains ACME account keys or TPM key descriptors in an accounts table indexed by service ID. Save and restore the entire state table. Before enrollment, it may contain account state without a device credential. Xedge uses encrypted xcfg.bin; the default runtime store uses acme/sharktrust.json. Protect this store as private key material when software keys are used.

Both SharkTrustX and SharkCA can restore a deleted service profile from this retained account identity and obtain another certificate without changing the registered device or name. After upgrading an existing installation, let it start successfully with its original service file present before deleting that file. An account identity already lost before the upgrade cannot be restored by this feature. For normal forced renewal, use runtime:renew() instead of deleting files.

The adapter calls store.load and store.save as plain functions, without an implicit self argument. Both functions must call their callback exactly once:

store.load(callback)

Parameters

Return values

Ignored. Report completion exactly once through callback.

Throws

Exceptions are caught and reported as storage_read_failed. Callback-reported errors use the same code, with the supplied message. Errors raised after completion do not replace the result.

store.save(state, callback)

Parameters

Return values

Ignored. Report completion exactly once through callback. The adapter serializes saves and retains failed state for a later resume() retry.

Throws

Exceptions and callback-reported errors become storage_write_failed errors with the supplied message. Other table error fields are not forwarded. Duplicate completions and exceptions after completion do not replace the result.

callback(state, nil) -- load: state was found
callback(nil, nil)   -- load: no state exists
callback(nil, err)   -- load: read failed

callback(true, nil)  -- save: durable commit completed
callback(nil, err)   -- save: commit failed

save() must not report success until the data is durably committed. A filesystem implementation can write a temporary file and rename it. The adapter serializes save operations.

{
   version = 2,
   portalUrl = "https://portal.example.com/sharktrust.lsp",
   zoneIdentity = "<opaque-non-secret-fingerprint>",
   deviceId = "0123456789abcdef0123",
   name = "controller.example-zone.com",
   credential = "<64-lowercase-hexadecimal-characters>",
   updatedAt = 1788105600
}

The credential is secret. Protect it with the strongest storage available on the target. Do not place this state in an application ZIP, source repository, public resource, log, or diagnostic download. The adapter rejects malformed state and state belonging to another portal or zone identity.

Automatic DNS Adapter Methods

Direct returns acknowledge submission; callbacks report completion and may run before the method returns. Use the runtime methods for ordinary application code.

challenge:enroll(request, callback)

Registers the device and saves its assigned identity.

Parameters

Return values

Throws

Callback errors are caught and traced. Reported storage or portal failures use the callback. Errors from incorrect arguments or supplied client/timer implementations can propagate.

challenge:isAvailable(name, callback)

Performs an advisory name-availability check; enrollment makes the final decision.

Parameters

Return values

Throws

Callback errors are caught and traced. Reported storage or portal failures use the callback. Errors from incorrect arguments or supplied client/timer implementations can propagate.

challenge:load(callback)

Loads and validates saved registration state. Concurrent loads share the same pending read.

Parameters

Return values

Throws

Callback errors are caught and traced. Reported storage or portal failures use the callback. Errors from incorrect arguments or supplied client/timer implementations can propagate.

challenge:resume(callback)

Confirms saved registration with the portal and persists a changed assigned name. First retries a pending failed state save.

Parameters

Return values

Throws

Callback errors are caught and traced. Reported storage or portal failures use the callback. Errors from incorrect arguments or supplied client/timer implementations can propagate.

challenge:isRegistered(callback)

Checks registration and updates the portal with the automatically discovered local IPv4 address.

Parameters

Return values

Throws

Callback errors are caught and traced. Reported storage or portal failures use the callback. Errors from incorrect arguments or supplied client/timer implementations can propagate.

challenge:setIpAddress(ipAddress, callback)

Updates the registered local IPv4 address after a network-address change.

Parameters

Return values

Throws

Callback errors are caught and traced. Reported storage or portal failures use the callback. Errors from incorrect arguments or supplied client/timer implementations can propagate.

challenge:getWan(callback)

Reads the public IPv4 address observed by the portal.

Parameters

Return values

Throws

Callback errors are caught and traced. Reported storage or portal failures use the callback. Errors from incorrect arguments or supplied client/timer implementations can propagate.

challenge:switchIdentity(value, options, callback)

Replaces the portal client. Changing the portal or zone identity requires enrollment and persistence before replacing the active registration.

Parameters

Return values

Throws

Callback errors are caught and traced. Reported storage or portal failures use the callback. Errors from incorrect arguments or supplied client/timer implementations can propagate.

challenge:present(context, callback)

Publishes the TXT record and waits for the propagation delay. Called by the ACME engine.

Parameters

Return values

Throws

Callback errors are caught and traced. Reported storage or portal failures use the callback. Errors from incorrect arguments or supplied client/timer implementations can propagate.

challenge:status()

Reads a non-secret status snapshot.

Parameters

None.

Return values

Throws

Does not throw for valid adapter state.

See the separate cleanup() and close() contracts below.

challenge:cleanup(context, callback)

Clears the active challenge and cancels its propagation timer. A pending present() callback receives nil and an error table with code challenge_cancelled. When registration state exists, cleanup requests removal of the TXT record.

Parameters

Return values

Throws

Removal failures are reported through the callback. Errors from the pending present() callback or completion callback are caught and traced. Incorrect use of a supplied client or timer may throw.

challenge:close([callback])

Cancels the pending challenge and its propagation timer. If a registration and active challenge exist, it attempts to remove the TXT record before closing the client. Client cleanup proceeds even when record removal fails.

Parameters

Return values

After shutdown has started, repeated calls return no values and invoke the supplied callback immediately with true, nil. They neither wait for the first call nor retry failed cleanup.

Throws

Reported removal and close failures are delivered to the callback. Completion callback errors are caught and traced. A supplied client must implement the documented methods and callback contracts; errors from incorrect client usage are not converted to cleanup error results.

switchIdentity() is required when the portal URL, zone key, zone secret, or generated proof identity changes. Without explicit re-enrollment permission, it returns sharktrust_identity_change_requires_reenrollment. An ACME production or staging change does not change SharkTrust identity.

challenge:switchIdentity(
   {client = newClient},
   {
      reenroll = true,
      enrollment = {
         name = "controller",
         namePolicy = "exact",
         dns = "local",
         info = "BAS controller"
      }
   },
   function(result, err)
      -- result.changed is true when the identity was replaced.
      -- result.state contains the newly saved registration state.
   end)

Dns.createManual(options)

Creates an operator-assisted DNS-01 adapter. Use it when the application cannot update DNS automatically and can show the TXT record to an operator.

local challenge = Dns.createManual {
   -- Code 32 tells the host to read challenge:status() and display the TXT record.
   notify = function(code) end
}

Parameters

Return values

Throws

Throws if options cannot be used as an options table. Notification callback errors are caught and traced when a notification is delivered.

The manual adapter implements present(context, callback) and cleanup(context, callback) for the engine. Application code uses these methods:

challenge:status()

Returns the information needed to display the pending DNS record.

Parameters

None.

Return values

Throws

Does not deliberately throw for a valid adapter and challenge context.

challenge:continue([callback])

Continues validation after the operator confirms that the TXT record is publicly visible.

Parameters

Return values

Throws

No pending action is reported as an error result. Errors raised by the engine or completion callback are caught and traced.

challenge:cancel([callback])

Cancels the pending operator action and returns the adapter to idle. The engine's pending present() callback receives nil and an error table with code challenge_cancelled.

The old action is cleared before its callback runs, so a callback can cancel again or start a new action without notifying the old action twice.

Parameters

Return values

Throws

Errors raised by the engine or completion callback are caught and traced.

challenge:close([callback])

Closes the adapter and cancels any pending action. Later engine present() calls fail with adapter_closed. Repeated close calls succeed.

Parameters

Return values

Throws

Errors raised by cancellation or completion callbacks are caught and traced.

The module does not send email. A host may display the manual adapter status in a user interface or forward it through its own email service.

Certificate Manager

The manager in acme/runtime owns persistent ACME state, certificate installation, renewal scheduling, revocation, and service profiles. Most applications should use acme/runtime. Use the manager directly only when the application must assemble these responsibilities separately from SharkTrust or other runtime services.

Runtime.createManager(options)

The manager is a lower-level API and therefore always requires a certificate installer. In the example, installCertificates is the host function defined by the certificate installation callback contract. Mako and Xedge automatic installation applies to Runtime.create(), not to a manager constructed directly.

local Runtime = require"acme/runtime"
local manager, err = Runtime.createManager {
   io = writableIo,
   engine = engine,
   install = installCertificates, -- host callback described above
   notify = function(code) end,
   renewAllowed = function(domain, expiresAt) return true end,
   path = "acme"
}

Parameters

Return values

Throws

A missing or incorrectly typed options argument can throw. Incorrect path types can throw during construction; I/O, engine and callback interfaces must satisfy their documented contracts and are used later.

manager:configure(config)

Sets the desired domains, ACME service, challenge adapter, and certificate-key policy. Call it before start() when using the manager directly. Calling it does not contact the ACME service.

local ok, err = manager:configure {
   email = "operator@example.com",
   domains = {"device.example.com"},
   acceptTerms = true,
   service = {production = false},
   challenge = challengeAdapter,
   key = {type = "ecc", curve = "SECP384R1"},
   cleanup = true,
   timeout = 600,
   dnsResolveTimeout = 30,
   fallbackRenewBefore = 22 * 24 * 60 * 60
}

Parameters

Return values

Throws

Configuration validation failures use the error results above. Supply the documented types and ordinary configuration tables; values that cannot be copied or used by the configured components may raise Lua errors. Errors from later operations are reported by those operations.

ECC certificate keys use the platform TPM when its key interface is available; otherwise they are created as software keys. RSA keys are always software keys.

Manager Methods

manager:load(callback)

Loads saved state and installs the active certificate set without contacting an ACME service. Repeated calls reinstall the loaded active profile without reading storage again. The runtime calls this during startup.

Parameters

Return values

Throws

Installer exceptions are converted to certificate_install_failed errors. Completion callback errors are caught and traced. Incorrect use of a supplied storage or scheduling interface may throw.

manager:start(callback)

Loads saved state if needed, obtains missing or expired certificates, installs the active set and enables renewal. Call after configure().

Parameters

Return values

If stop() is called while startup is loading state, that startup cannot later enable renewal. Its callback receives nil, err with code manager_stopped. close() similarly prevents the pending startup, with code manager_closed. Pending storage and installation work is allowed to finish. A subsequent explicit start() can resume a stopped manager.

Throws

Reported storage, issuance and installation failures use the callback. Installer exceptions become certificate_install_failed errors. Completion callback errors are caught and traced. Incorrect configuration or supplied components may raise Lua errors.

manager:stop([callback])

Stops the automatic renewal timer while leaving explicit management methods available. This does not cancel work already in progress, but a pending startup cannot re-enable renewal after this call. Use close() for shutdown.

Parameters

Return values

Throws

Callback errors are caught and traced. Incorrect use of a supplied timer interface may throw.

manager:switchService(service, options, callback)

Changes the active ACME directory and its account/certificate profile. Switching to the same directory updates the service settings without rebuilding certificates.

Parameters

Return values

The manager retains the previous active profile until installation and persistence succeed. If saving the active-profile selection fails after installation, it asks the installer to restore the previous set. An unsuccessful restoration is reported in err.rollback (table). The host's installer determines the effect of partial installation and empty sets.

Throws

Reported preparation, storage and installation failures use the callback. Installer exceptions become certificate_install_failed errors. Callback errors are caught and traced. Incorrect arguments or supplied components may raise Lua errors.

manager:renew(domain, options, callback)

Checks or forces renewal of one managed certificate, then installs the current set.

Parameters

Return values

Throws

Reported issuance, persistence and installation failures use the callback. Installer exceptions become certificate_install_failed errors; completion callback errors are caught and traced. Incorrect options or supplied components may raise Lua errors.

manager:revoke(domain, options, callback)

Requests revocation, removes the saved record, persists the profile, and passes the remaining set to the installer. Removing the last record retains the installed certificates when using the built-in installer.

Revocation does not remove the domain from configuration. If the domain remains configured, a later startup can obtain a new certificate because its saved record is now missing. Remove the domain from the host configuration as well if the host should stop managing certificates for that name.

Parameters

Return values

Throws

Reported service, storage and installation failures use the callback. Installer exceptions become certificate_install_failed errors; completion callback errors are caught and traced. Incorrect options or supplied components may raise Lua errors.

manager:status()

Returns an operational snapshot without certificate private keys.

Parameters

None.

Return values

Renewal dates may be more than 49.7 days away. The manager splits long waits into timer intervals of at most 4294967295 milliseconds and checks the due dates again after each interval. An intermediate wakeup does not renew a certificate before its scheduled date.

Throws

No error for a valid manager and supported engine. Errors from a supplied engine's jobs() method propagate.

manager:domains()

Reads managed certificate records, including private keys. Use status() for public diagnostics.

Parameters

None.

Return values

Throws

Does not throw for valid managed records.

manager:certificate(domain)

Parameters

Return values

Throws

Does not throw for a valid domain argument and managed record.

manager:account()

Parameters

None.

Return values

Throws

Does not throw for a valid managed account.

manager:close([callback])

Stops the renewal timer and closes the engine. Call during shutdown when using the manager directly.

Parameters

Return values

While a certificate installer is pending, returns nil, err (string), where err is "busy". The manager remains open, its timer is unchanged, and the close callback is not called. The caller must retry after installation finishes. No close retry is queued internally.

Throws

Reported engine close failures are passed to the callback. Completion callback errors are caught and traced. Incorrect use of a supplied engine or timer may throw.

domains(), certificate(), and account() return trusted management data that can include private keys. Do not use these values as status or logging data.

Certificate Record

-- Certificate record returned by manager accessors
{
   domain = "device.example.com",
   privateKey = privateKey,
   certificate = pemCertificateChain,
   expiresAt = unixTime,
   renewAt = unixTime,
   ariCheckAt = optionalUnixTime,
   ariId = optionalAriIdentifier,
   explanationUrl = optionalUrl,
   orderUrl = "https://acme.example/order/123",
   directoryUrl = "https://acme.example/directory",
   issuedAt = unixTime
}

The manager uses ACME Renewal Information (ARI) when the service advertises RFC 9773 support. Otherwise, it schedules renewal from the certificate expiration time with a small random offset. Retryable renewal failures back off from 30 seconds to five minutes. A permanent failure is checked again after six hours.

A scheduled check remains busy until certificate installation completes. Installation failure is recorded in status().lastError (table), with code certificate_install_failed, and retried after six hours under the existing non-temporary error policy. The retry installs the saved certificate set; it does not request another certificate unless renewal is due. If renewal or refresh also failed, its error remains primary and lastError.install (table) contains the installation error. A successful retry clears retryAt; lastError remains the most recent recorded failure.

SharkTrust Client

Dns.createClient() creates the low-level SharkTrust device client. It handles device enrollment, authenticated portal commands, DNS updates, and reverse connections. It is not an ACME client and does not persist credentials. Most applications use acme/runtime, which creates this client and the persistent DNS adapter automatically.

Dns.identity(options)

Resolves portal identity without making a network request. createClient() calls this resolver automatically.

Parameters

Return values

Otherwise, returns a copy of options with portalUrl (string, prefixed with https://), zoneKey (hexadecimal string), and proof (function) from the compiled identity module. It first loads etokengen, falling back to tokengen only if that load fails. If neither loads, or the selected module lacks a proof function, returns nil, err (table) with code sharktrust_not_configured. Keep the resulting identity and proof callback private.

Throws

Incorrect option types or an incompatible compiled identity module may throw. Errors raised by the selected module's info() function propagate. Missing compiled identity support is reported as nil, err.

Dns.createClient(options)

Creates a client for one SharkTrust portal and zone. Omit the identity fields, or the entire options table, to use the compiled identity through Dns.identity(). Call it directly only when an application needs low-level SharkTrust operations or when constructing Dns.createSharkTrust() without the runtime.

local Dns = require"acme/dns"
local client, err = Dns.createClient {
   portalUrl = "https://portal.example.com/sharktrust.lsp",
   zoneKey = "<64-hexadecimal-character-zone-key>",
   proof = hostProof, -- host callback described below; no secret option
   credential = savedDeviceCredential,
   http = httpOptions,
   reverse = reverseOptions
}

Parameters

The optional reverse table accepts these native reverse-connection settings. The SharkTrust client supplies url and the authentication headers:

Return values

Throws

Proof callback errors are caught and reported as proof_failed. Incorrect option types, compiled-identity interfaces or native hashing setup can raise Lua errors.

Host Proof Callback

The host supplies proof(message), a synchronous function accepting the exact binary-safe string to authenticate, including any NUL bytes. It must return a 32-byte binary HMAC-SHA-256 result and must not yield. The client constructs the message and base64url-encodes the result for X-SharkTrust-Proof. Do not change the message or encode the result in the callback. The client neither receives the zone secret nor derives its proof key.

A compiled tokengen.proof or etokengen.proof function can be supplied directly. For host-provisioned secrets, this helper recalculates the key on each call:

-- Capture globals during mako.conf evaluation for later callback calls.
local ba,string,tonumber=ba,string,tonumber
local function makeProof(zoneKey,secret)
   return function(message)
      local salt=zoneKey:gsub("%x%x",function(pair) return string.char(tonumber(pair,16)) end)
      local key=ba.crypto.PBKDF2("sha256",secret:upper(),salt,1000,32)
      return ba.crypto.hash("hmac","sha256",key)(message)(true,"binary")
   end
end

makeProof(zoneKey, secret) is an illustrative host helper, not a client API. Both required arguments are strings of exactly 64 hexadecimal characters, with no defaults. It returns the callback. Assign hostProof=makeProof(zoneKey, zoneSecret), where zoneSecret is the host-provisioned secret string. Capture globals during mako.conf evaluation as shown: its loader removes the global fallback before later callback calls.

The helper uses Password-Based Key Derivation Function 2 (PBKDF2) with HMAC-SHA-256 to derive its proof key. Its password is the secret's 64 uppercase ASCII characters; its salt is the hex-decoded 32-byte zone key. It uses 1000 iterations and produces 32 bytes. Do not hex-decode the secret. The callback then authenticates the unchanged message with HMAC-SHA-256 using that derived key. Recalculating the key uses more CPU than deriving it once; this helper retains only the host-provided credentials between calls.

The host supplies its configuration and callback again on startup. The core does not persist them. Xedge's settings UI retains its own encrypted credential configuration and supplies a callback internally. Portal-issued device credentials and ACME account/certificate state still require persistence.

SharkTrust Client Methods

Use this table to find a method, then follow its link for argument types, return values, and callback behavior.

MethodWhen to use it
client:isAvailable()Check name availability before enrollment. This does not reserve the name.
client:enroll()Register a device when your application owns credential persistence. The DNS adapter normally does this.
client:isRegistered()Confirm a saved registration. Runtime startup performs this check automatically.
client:setIpAddress()Report an address change after startup. Runtime users should call runtime:setIpAddress().
client:setAcmeRecord()Publish a validation TXT record. Normally called by the DNS adapter's present() method.
client:removeAcmeRecord()Remove a validation TXT record. Normally called during DNS adapter cleanup.
client:getWan()Read the public IPv4 address observed by the portal.
client:reverseConnection()Enable or disable portal-assisted remote access. Runtime users should call runtime:reverseConnection().
client:reverseStatus()Read reverse-connection status for a display or diagnostic check.
client:credential()Read the secret device credential for private persistence. Normally used by the DNS adapter.
client:setCredential()Restore or clear a device credential. Normally used by the DNS adapter.
client:identity()Read portal identity for diagnostics or to detect saved state from another portal or zone.
client:close()Release the client during shutdown. The runtime and DNS adapter close their clients automatically.

client:isAvailable(name, callback)

Parameters

Return values

Throws

Reported transport, portal and proof failures use the callback. Proof callback errors become proof_failed; completion callback errors are caught and traced. Incorrect arguments or errors raised by supplied/native components may propagate.

client:enroll(request, callback)

Parameters

Return values

Throws

Reported transport, portal and proof failures use the callback. Proof callback errors become proof_failed; completion callback errors are caught and traced. Incorrect arguments or errors raised by supplied/native components may propagate.

client:isRegistered(callback)

Parameters

Return values

Throws

Reported transport, portal and proof failures use the callback. Proof callback errors become proof_failed; completion callback errors are caught and traced. Incorrect arguments or errors raised by supplied/native components may propagate.

client:setIpAddress(ipAddress, callback)

Parameters

Return values

Throws

Reported transport, portal and proof failures use the callback. Proof callback errors become proof_failed; completion callback errors are caught and traced. Incorrect arguments or errors raised by supplied/native components may propagate.

client:setAcmeRecord(request, callback)

Parameters

Return values

Throws

Reported transport, portal and proof failures use the callback. Proof callback errors become proof_failed; completion callback errors are caught and traced. Incorrect arguments or errors raised by supplied/native components may propagate.

client:removeAcmeRecord(callback)

Parameters

Return values

Throws

Reported transport, portal and proof failures use the callback. Proof callback errors become proof_failed; completion callback errors are caught and traced. Incorrect arguments or errors raised by supplied/native components may propagate.

client:getWan(callback)

Parameters

Return values

Throws

Reported transport, portal and proof failures use the callback. Proof callback errors become proof_failed; completion callback errors are caught and traced. Incorrect arguments or errors raised by supplied/native components may propagate.

client:reverseConnection([enable])

Parameters

Return values

On failure returns nil, err (table). Errors include client_closed, not_enrolled, reverse_connection_unavailable and proof_failed. Except when already closed, the requested enable setting is retained even if startup fails. A later credential update attempts to restart an enabled connection.

Throws

Proof callback errors are converted to proof_failed. Incorrect native reverse-connection options or incompatible supplied components may throw.

client:setCredential(credential)

Parameters

Return values

Setting a credential attempts to restart an enabled reverse connection. The true result confirms storage only; a returned restart error is not forwarded. Inspect reverseStatus() for connection state.

Throws

Invalid credential values are reported as nil, err. Errors raised by native options or supplied components during reverse restart may propagate.

client:close([callback])

Clears credential and proof references and closes reverse and HTTP clients.

Parameters

Return values

Throws

Completion callback errors are caught and traced. Errors from incorrect use of supplied components may propagate. Return values from underlying transport close methods are not forwarded.

client:reverseStatus()

Parameters

None.

Return values

Closing the client stops its reverse connection but does not reset the stored enable setting. Use connected to inspect connection state.

Throws

No error for a valid client using the supported native reverse interface. Errors raised by a custom reverse interface's status() method propagate.

client:credential()

Parameters

None.

Return values

Throws

Does not throw for a valid client.

client:identity()

Parameters

None.

Return values

Throws

Does not throw for a valid client.

Enrollment and Device Names

client:enroll({
   name = "controller",
   namePolicy = "exact",
   dns = "local",
   info = "Xedge"
}, function(result, err) end)

namePolicy="exact" requires the requested name and returns name_unavailable on a conflict. namePolicy="increment" lets the portal try numbered variants such as controller1 and controller2. If a name is supplied without a policy, the policy is exact. If no name is supplied, the portal assigns one.

The normal DNS adapter saves a random enrollment credential before contacting the portal and reuses it after failures or restarts. The updated portal recovers an existing registration by that credential before checking name availability. A different credential requesting an occupied exact name leaves the old device unchanged, triggers an immediate email attempt to the portal log recipient, and keeps the runtime retrying once per hour. Same-credential recovery does not send a conflict email. Deploy the updated portal before this client.

Selecting the Published Address

The dns field selects the ordinary DNS A record for the enrolled device. It does not select HTTP-01 or DNS-01.

ValuePublished addressNetwork effect
localThe device's local IPv4 address, discovered automatically at startup and updated through setIpAddress() after a later network change.Use this when local clients should connect directly to the device. This is the default.
wanThe public source address observed by the portal.Remote access still requires suitable router and firewall rules.
bothBoth local and public addresses.Public DNS exposes the private address and clients may try either address.

both is ordinary multi-address DNS. It is not split-horizon DNS and does not guarantee failover. Publishing wan does not create a NAT, port-forwarding, or firewall rule. Reverse connection is separate and routes public requests through the portal.

Publishing an ACME TXT Record

Application code normally does not call setAcmeRecord() or removeAcmeRecord(). The SharkTrust DNS adapter calls them from its engine-facing present() and cleanup() methods. This request format is documented for developers implementing or testing a custom DNS integration.

client:setAcmeRecord({
   recordName = "_acme-challenge.controller.example.com",
   recordData = "base64url-acme-validation-value",
   dnsResolveTimeoutMs = 30000
}, function(result, err) end)

Module acme/engine

acme/engine is the low-level RFC 8555 client. It communicates with an ACME directory but does not own persistent storage, renewal timers, certificate installation, Mako configuration, Xedge events, or SharkTrust enrollment. Most applications should use acme/runtime. Use the engine directly when implementing a custom manager or challenge workflow.

Engine.create(options)

Creates a low-level ACME client and its serialized certificate-job queue.

local Engine = require"acme/engine"
local engine = Engine.create {
   tpm = tpmInterface,
   now = os.time
}

Parameters

Mako and Xedge install their platform TPM callbacks before publishing ba.tpm. Those trusted callbacks take precedence over constructor injection.

Return values

Throws

Incorrect options or dependency types may raise Lua errors. For valid options, construction has no reported operational error return.

Account Object

The account argument used by certificate and revocation operations has this form:

{
   email = "operator@example.com",
   key = accountPrivateKey,
   url = "https://acme.example/acct/123",
   directoryUrl = "https://acme.example/directory"
}

The caller must persist the account returned by certificate operations. The manager does this automatically.

Engine Methods

engine:directory(service, callback)

Downloads and validates an ACME directory to inspect provider capabilities.

Parameters

Return values

Throws

Reported transport and response errors go to the callback. A directory requiring External Account Binding returns external_account_required. Malformed JSON returns invalid_response; missing or invalid endpoint URLs return invalid_directory. Callback errors are caught and traced. Incorrect arguments or supplied components may raise Lua errors.

engine:terms(service, callback)

Reads the provider's Terms of Service URL before the application accepts the terms.

Parameters

Return values

Throws

Uses the same directory validation and operational error reporting as directory(). Callback errors are caught and traced. Incorrect arguments or supplied components may raise Lua errors.

engine:certificate(service, account, request, callback)

Queues one certificate order and immediately returns a job object. Use it when a custom manager owns account persistence, certificate persistence, installation, and renewal. Normal applications use the runtime or manager.

-- Use the callback for completion and check immediate submission errors.
local job, err = engine:certificate(service, account, {
   domain = "device.example.com",
   acceptTerms = true,
   challenge = challengeAdapter,
   key = {
      type = "ecc",
      curve = "SECP384R1"
   },
   replaces = optionalAriIdentifier,
   timeout = 600,
   dnsResolveTimeout = 30
}, function(result, requestErr) end)

Parameters

ECC defaults to SECP384R1 and automatically uses the configured TPM interface. RSA defaults to 2048 bits and always uses a software key. New ACME account keys always use P-256.

The engine processes every authorization. An authorization already marked valid skips challenge presentation, triggering and cleanup. Pending authorizations use the selected challenge adapter as before. These low-level options do not by themselves enable SharkCA registration or IP-change replacement in the runtime or certificate manager.

Return values

Throws

Invalid submission arguments or worker setup can throw. Errors inside the certificate coroutine are caught and reported as operation_failed; reported operational failures go to the callback. String errors from native key and CSR creation become operation_failed error tables with the original message. Callback errors are caught and traced. Challenge cleanup is attempted on failure when a challenge context exists.

engine:renewalInfo(service, certificate, callback)

Reads ACME Renewal Information (ARI). The manager normally performs this check.

Parameters

Return values

Throws

Reported HTTP, certificate and response errors use the callback. Malformed JSON produces invalid_response; a missing or invalid suggested window produces invalid_renewal_info. A provider without ARI returns ari_unavailable. Callback errors are caught and traced. Incorrect arguments or supplied components may raise Lua errors.

engine:revoke(service, account, certificate, options, callback)

Asks the ACME service to revoke a certificate. This method does not remove saved records or change the installed certificates. Normal applications use manager:revoke().

Parameters

Return values

Throws

Reported transport, signing and service failures go to the callback. A signing error string becomes a sign_failed error table; a signer's error table is preserved. No signed request is sent after signing fails. An account from a different directory returns account_directory_mismatch; missing PEM certificate data returns invalid_certificate. Callback errors are caught and traced. Incorrect arguments, invalid signing keys or supplied components may raise Lua errors.

engine:prepareAccount(account)

Prepares an account key synchronously without contacting an ACME service. A custom integration can save the key before associating it with an authenticated device.

Parameters

Return values

There is no callback or automatic persistence. Save the updated account before sending its thumbprint to an enrollment service. Restoring the saved account and calling this method again reuses its key. A later certificate operation still returns the service-assigned account URL, which must also be saved.

Throws

Incorrect arguments and exceptions from supplied TPM methods propagate, as with createKey().

engine:createKey(name, options)

Creates a certificate key synchronously for a custom integration. ECC uses the configured TPM interface when available; otherwise it uses a software key. RSA always uses a software key.

Parameters

Return values

Throws

Invalid software key settings throw as described for ba.create.key(). Errors raised by the supplied TPM interface propagate. This synchronous method does not catch Lua errors or invoke a completion callback.

engine:jobs()

Lists outstanding certificate requests for diagnostics.

Parameters

None.

Return values

Throws

Does not throw for valid job state.

engine:close([callback])

Cancels queued work, performs required challenge cleanup, closes owned HTTP clients, and prevents new work. Call during shutdown when using the engine directly.

Parameters

Return values

Throws

Challenge cleanup exceptions are converted to error tables. Completion callback errors are caught and traced. Incorrect use of supplied components may throw.

Challenge Adapter Interface

A challenge adapter makes an ACME validation value reachable and removes it afterward. Runtime users normally select the built-in HTTP adapter, the SharkTrust DNS adapter, or the manual DNS adapter. Implement this interface only for another DNS provider or hosting environment.

challenge.type = "dns-01" -- or "http-01"
challenge:present(context, callback)
challenge:cleanup(context, callback)

For DNS-01, context has this form:

{
   domain = "controller.example.com",
   token = "token-from-acme-server",
   keyAuthorization = "token-and-account-thumbprint",
   challengeUrl = "https://acme.example/challenge/123",
   recordName = "_acme-challenge.controller.example.com",
   recordData = "base64url-acme-validation-value",
   dnsResolveTimeoutMs = 30000
}

For HTTP-01, context has this form:

{
   domain = "controller.example.com",
   token = "token-from-acme-server",
   challengeUrl = "https://acme.example/challenge/123",
   tokenPath = "acme-challenge/token-from-acme-server",
   keyAuthorization = "token-and-account-thumbprint"
}

All context members are supplied by the engine. The adapter must not change them. After a successful present(), the engine calls cleanup() for success, failure, timeout, cancellation, or callback error.

Certificate Job

engine:certificate() returns a job immediately. Use the job only to inspect or cancel a low-level certificate request. Runtime and manager users normally do not handle job objects.

job:id()

Parameters

None.

Return values

Throws

Does not throw for a valid job.

job:status()

Reads progress or the final outcome of a retained job object. Completion is also delivered through the certificate callback.

Parameters

None.

Return values

Throws

Does not throw for valid job state.

job:cancel([callback])

Cancels a queued or running operation and performs challenge cleanup when required.

Parameters

Return values

Throws

Challenge cleanup exceptions are converted to error tables. Errors from the certificate and cancellation callbacks are caught and traced.

TPM Interface

The optional TPM interface stores ECC private keys outside Lua and performs signing or certificate-request operations with those keys. A standalone host implements these synchronous functions. Normal Mako and Xedge applications do not provide this table.

local tpmInterface = {
   hasKey = function(name) end,
   createKey = function(name, options) end,
   jwtSign = function(name, payload, protectedHeader) end,
   keyParams = function(name) end,
   createCsr = function(name, distinguishedName,
      certificateTypes, keyUsages) end
}
hasKey(name)

Parameters

Return values

Throws

Platform exceptions propagate to the calling engine operation.

createKey(name, options)

Parameters

Return values

Ignored by the ACME layer; a direct nil, err return is not recognized as a creation failure.

Throws

Platform exceptions propagate to the calling engine operation.

jwtSign(name, payload, protectedHeader)

Parameters

Return values

Throws

Platform exceptions propagate to the calling engine operation.

keyParams(name)

Parameters

Return values

Throws

Platform exceptions propagate to the calling engine operation.

createCsr(name, distinguishedName, certificateTypes, keyUsages)

Parameters

Return values

Throws

Platform exceptions propagate to the calling engine operation.

Engine.setTPM(tpmInterface)

Parameters

Return values

None. Installs the trusted callbacks and removes setTPM from the module, allowing only one installation.

Throws

Incorrect interface types can throw. Calling Engine.setTPM again fails because that function no longer exists. Individual platform callbacks are used and checked later by engine operations.

Certificate jobs catch Lua errors from platform callbacks and report operation_failed. Direct createKey() and standalone revocation do not provide that coroutine error handling; see their method contracts.

HTTPS Trust Settings

The ACME service and SharkTrust or SharkCA portal must present an HTTPS certificate trusted by the device. By default, the shared acme/http module uses ba.sharkclient(). If your portal uses a private CA, configure its trust store before creating the runtime or other clients.

Http.setCertStore(store)

Call require"acme/http".setCertStore(store) to set the shared default for subsequently created ACME HTTP clients.

Returns no values. Store validation and construction follow ba.create.sharkssl(), including its Lua errors. The setting is shared across applications using this module; it does not replace HTTP clients that already exist. An explicit http.shark client option takes precedence.

Host Logging

Mako and Xedge log the numeric notification codes and the optional message directly. A custom host may translate them into readable messages in its own notify(code, msg) callback. Use operation callbacks for detailed errors and runtime:status() for non-secret diagnostics.

Persistent State and Upgrades

The manager stores state below acme/ by default. Each effective ACME directory has an independent profile below acme/services/, and acme/active.json selects the installed profile. A Mako SharkTrust integration stores device enrollment separately in acme/sharktrust.json.

The modules read only their current state format. They do not import the old cert/ layout, acme-v2/, legacy devkey or refresh-token state, or account and certificate files from an earlier implementation.

Account keys, certificate private keys, SharkTrust device credentials, zone secrets, and proof material are secrets. Keep the writable I/O private and exclude it from application packages, source repositories, diagnostics, and downloads.

Current Protocol Limits

The normal public-CA runtime requests one DNS identifier per ACME order and manages each entry in domains as a separate certificate record. It does not provide a multi-name certificate through that list.

The SharkCA profile supports a device name, an approved local IPv4 address, or both in one certificate. The low-level engine:certificate() also accepts an explicit identifiers array. The caller must supply validated identifiers and a compatible service and validation flow.

The modules do not implement:

SharkCA does not support DNS commands, reverse connections, or RSA keys. Directory changes require closing and recreating its runtime.